#include "filter.h"

#include <QRgb>
#include <ctime>
#include <QEventLoop>
#include <QCoreApplication>
#include <algorithm>
#include <QDebug>

/// Parameter:
/// Return:
/// Action: Constructor
Filter::Filter()
{
    _isParallel = false;
    _filterMatrix = 0;
    _originalData = 0;
    _modifiedData = 0;
    _cl = 0;

    //_cl = new OpenCL();
}

/// Parameter:
/// Return:
/// Action: Destructor
Filter::~Filter()
{
    delete[] _filterMatrix;
    //DON'T DELETE ORIGINAL DATA
    delete[] _modifiedData;
    delete _cl;
}

/// Parameter: bias: filter's bias parameter
/// Return:
/// Action: sets bias for transformation
void Filter::set_bias(const float bias)
{
    _bias = bias;
}

/// Parameter: factor: filter's factor parameter
/// Return:
/// Action: sets factor for transformation
void Filter::set_factor(const float factor)
{
    _factor = factor;
}

/// Parameter: filterMatrix: matrix to be used for transformation
///            filterSize: size of the filterMatrix
/// Return:
/// Action: Create a dynamic array for the matrix and copies it.
void Filter::set_filter_matrix(const float* filterMatrix, const int filterSize)
{
    if (_filterMatrix != 0)
        _filterMatrix = new float[filterSize * filterSize];
    else
    {
        delete[] _filterMatrix;
        _filterMatrix = new float[filterSize * filterSize];
    }

    std::copy(filterMatrix, filterMatrix + filterSize * filterSize, _filterMatrix);

    _filterSize = filterSize;
}

/// Parameter: data: an array (pointer) containing the pixel values of the original image
///            width: original image's width
///            height: original image's height
/// Return:
/// Action: Sets the image properties.
void Filter::set_original_data(int* data, const int width, const int height)
{
    _originalData = data;

    _dataWidth = width;
    _dataHeight = height;
}

/// Parameter: isParallel: true: use OpenCL, false: use sequential algorithm
///            platform: use this platform for OpenCL
///            device: use this device for OpenCL
/// Return:
/// Action:  Sets the parallel properties.
void Filter::set_parallel(const bool isParallel, cl::Platform platform, cl::Device device)
{
    _isParallel = isParallel;

    if (isParallel)
    {
        if (_cl == 0)
            _cl = new OpenCL(platform, device);
        else
        {
            delete _cl;
            _cl = new OpenCL(platform, device);
        }
    }
}

/// Parameter:
/// Return:
/// Action: Disables parallel transformation.
///         Sequential algorithm is used.
void Filter::disable_parallel()
{
    disconnect(_cl, SIGNAL(progress(int)), this, SLOT(progress_cl(int)));
    _isParallel = false;

    if (_cl != 0)
    {
        delete _cl;
        _cl = 0;
    }
}

/// Parameter:
/// Return: Modified array containing pixel values to construct image
/// Action: Runs the filter for the loaded data.
int* Filter::use_filter()
{
    if (_modifiedData != 0)
    {
        delete[] _modifiedData;
        _modifiedData = 0;
    }

    if (_isParallel)
        return use_parallel_filter();
    else
        return use_non_parallel_filter();
}

/// Parameter:
/// Return: Modified array containing pixel values to construct image
/// Action: Loads the data to OpenCL and runs the parallel transformation for the loaded data
int* Filter::use_parallel_filter()
{
    _cl->set_bias(_bias);
    _cl->set_factor(_factor);
    _cl->set_filter_buffer(_filterMatrix, _filterSize);

    if (_cl->_result != 0)
        _cl->_result = 0;

    _cl->set_image_buffer(_originalData, _dataWidth, _dataHeight);

    disconnect(_cl, SIGNAL(progress(int)), this, SLOT(progress_cl(int)));
    connect(_cl, SIGNAL(progress(int)), this, SLOT(progress_cl(int)));

    _modifiedData = _cl->run_kernel();

    return _modifiedData;
}

/// Parameter:
/// Return: Modified array containing pixel values to construct image
/// Action: Runs the sequential algorithm for the loaded data.
///         Sends signals to monitor progress.
int* Filter::use_non_parallel_filter()
{
    _modifiedData = new int[_dataWidth * _dataHeight];

    for (int i = 0; i < _dataWidth * _dataHeight; ++i)
    {
        if (!__canTransform)
            goto FREE_RESOURCE;

        int x = i % _dataWidth;
        int y = i / _dataWidth;

        float red = 0.0, green = 0.0, blue = 0.0;

        for (int filterX = 0; filterX < _filterSize; ++filterX)
        {
            for (int filterY = 0; filterY < _filterSize; ++filterY)
            {
                int imageX = (x - _filterSize / 2 + filterX + _dataWidth) % _dataWidth;
                int imageY = (y - _filterSize / 2 + filterY + _dataHeight) % _dataHeight;

                int pixel = _originalData[imageY * _dataWidth + imageX];

                red += qRed(pixel) * _filterMatrix[filterY * _filterSize + filterX];
                green += qGreen(pixel) * _filterMatrix[filterY * _filterSize + filterX];
                blue += qBlue(pixel) * _filterMatrix[filterY * _filterSize + filterX];
            }
        }

        red = qMin(qMax((int)(red * _factor + _bias), 0), 255);
        green = qMin(qMax((int)(green * _factor + _bias), 0), 255);
        blue = qMin(qMax((int)(blue * _factor + _bias), 0), 255);

        QRgb newPixel = qRgb(red, green, blue);
        _modifiedData[y * _dataWidth + x] = newPixel;

        // Too frequent emits slow down the progressbar, as it cannot process fast enough the signals.
        if(i % 10000 == 0)
        {
            emit progress(i);
            QCoreApplication::processEvents(QEventLoop::AllEvents);
        }
    }
    emit progress(_dataWidth * _dataHeight - 1);

    return _modifiedData;

    // If transformation is canceled, we have to free resources,
    // and signal the backend that the transformation is "finished"
    FREE_RESOURCE:
    delete[] _modifiedData;
    _modifiedData = 0;
    emit progress(_dataWidth * _dataHeight - 1);
    return 0;
}

/// Parameter: progress value
/// Return:
/// Action: Forwards value to the backend
void Filter::progress_cl(int v)
{
    progress(int(v));
}
